/**
 * 
 */
package com.googlecode.jstructs.instr;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.Value;

public class JStructClassInstr extends ClassAdapter {
	int version;
	String name;
	final Map<String, byte[]> createdClasses = new HashMap<String, byte[]>();
	private final StructInstrumentation si;
	private final ClassLoader loader;

	JStructClassInstr(StructInstrumentation si, ClassLoader loader,
			ClassVisitor cv) {
		super(cv);
		this.si = si;
		this.loader = loader;
	}

	public boolean isValueType(String className) {
		return si.isValueType(loader, className);
	}

	public ClassVisitor underliningVisitor() {
		return cv;
	}

	@Override
	public void visit(int version, int access, String name, String signature,
			String superName, String[] interfaces) {
		this.version = version;
		this.name = name;
		super.visit(version, access, name, signature, superName, interfaces);
	}

	@Override
	public MethodVisitor visitMethod(int access, String name, String desc,
			String signature, String[] exceptions) {
		return new MethodInstr(this, new MethodNode(access, name, desc,
				signature, exceptions));

	}

	public void methodVisitEnd(MethodInstr m) {
		MethodNode mn = m.mn;

		String[] exceptions = (String[]) mn.exceptions
				.toArray(new String[mn.exceptions.size()]);
		mn.accept(cv.visitMethod(mn.access, mn.name, mn.desc, mn.signature,
				exceptions));

		if (!m.needsInstrumentation)
			return;

		System.out.println(mn.name);
		MethodNode mn2 = new MethodNode(mn.access, mn.name, mn.desc,
				mn.signature, exceptions);
		mn.accept(mn2);
		ArrayList<Variable> oldVars = new ArrayList<Variable>();
		int maxVar = 0;
		for (int i = 0; i < mn2.instructions.size(); i++) {
			AbstractInsnNode node = mn2.instructions.get(i);
			if (node instanceof VarInsnNode) {
				VarInsnNode vnode = (VarInsnNode) node;
				Variable vn = new Variable(vnode.var);
				maxVar = Math.max(maxVar, vnode.var);
				mn2.instructions.set(vnode, new VarInsnNode2(vnode, vn));
			}
		}

		Analyzer analyzer = new Analyzer(new BasicInterpreter() {
			@Override
			public Value newValue(Type type) {
				if (type != null && type.getSort() == Type.OBJECT) {
					return new BasicValue(type);
				}
				return super.newValue(type);
			}

			@Override
			public Value merge(Value v, Value w) {
				if (v != w && v != null && w != null && !v.equals(w)) {
					Type t = ((BasicValue) v).getType();
					Type u = ((BasicValue) w).getType();
					if (t != null && u != null && t.getSort() == Type.OBJECT
							&& u.getSort() == Type.OBJECT) {
						return BasicValue.REFERENCE_VALUE;
					}
				}
				return super.merge(v, w);
			}
		}) {
			@Override
			protected Frame newFrame(Frame src) {
				return new DataFlowFrame(src);
			}

			@Override
			protected Frame newFrame(int locals, int stack) {
				return new DataFlowFrame(locals, stack);
			}
		};

		try {
			analyzer.analyze(name, mn2);
		} catch (AnalyzerException e) {
			e.printStackTrace(System.err);
			// /throw new RuntimeException(e);
		}
	}
}